#include <linux/module.h>
#include <linux/errno.h>		/* para -ERESTARTSYS */
#include <linux/kernel.h>
#include <asm/semaphore.h> 		/* para los semaforos */
#include <linux/fs.h>			/* para las file_operations */
#include <asm/uaccess.h>		/* para transferencia user<->kernel */
#include <linux/wait.h>			/* para las colas de espera */
#include "rbuffer.h"			/* para el buffer circular */
#include <linux/sched.h>		/* para las operaciones sobre colas */

#define DEBUG
#include "debug.h"
#include "fifoops.h"

static int fifo_open(struct inode *ip, struct file *fp)
{
	dprintk ("fifo: Open.\n");
	return 0;
}

static ssize_t fifo_read(struct file *fp, char *buf, size_t count,
						 loff_t *offset)
{
	/** buf en espacio usuario => no se puede desreferenciar directamente! **/
	/** esta es la funcion analoga a "consumidor" del teorico **/
	int i = 0;
	int err = 0;
	byte * tmp = NULL; /* auxiliar donde se va guardando lo leido */
	
	tmp = (byte *) kcalloc (count, sizeof(byte), GFP_KERNEL);

	if (down_interruptible (&sem_read))
		return -ERESTARTSYS; /* sleep interrumpido */

	while (i < count) /* no termine de leer */
	{
		if (rbuffer_is_empty (buffer)) /* buffer vacio => duermo lector */
			interruptible_sleep_on(&cola_read);

		if (down_interruptible (&sem_buffer))
			return -ERESTARTSYS;
		
		err = rbuffer_remove(buffer, &tmp[i]);
		up(&sem_buffer);

		if (err) /* error durante rbuffer_remove, buffer vacio */
		{
			wake_up_interruptible(&cola_write);
			interruptible_sleep_on(&cola_read);
			up(&sem_read);
		}
		else
		{
			i+=1;
			/* despertamos posible productor dormido */
			wake_up_interruptible(&cola_write); 
		}
	}

	err = copy_to_user(buf, tmp, i); /*kernel -> usuario*/
	kfree(tmp);
	if (err != 0) /* error al copiar */
	{
		dprintk ("¡Error durante la copia to_user!\n");
		up(&sem_read);
		return -1;
	}
	else
	{
		up(&sem_read);
		return i;
	}
}
static ssize_t fifo_write(struct file *fp, const char *buf, size_t count,
						  loff_t *offset)
{
	int i = 0;
	int err = 0;
	byte * buf_kernel = NULL;

	dprintk ("COUNT = %d\n", count);

	buf_kernel = (byte *) kcalloc (count, sizeof(byte), GFP_KERNEL);

	if (down_interruptible (&sem_write))
		return -ERESTARTSYS;

	/* usuario -> kernel */
	i = copy_from_user(buf_kernel, buf, count);

	if (i!=0) /* falló la copia */
	{
		dprintk ("¡Error durante la copia to_kernel!\n");
		up(&sem_write);
		return -1;
	}
	else
	{
		while (i < count)
		{
			if (rbuffer_is_full (buffer)) /* buffer lleno=> duermo consumidor*/
				interruptible_sleep_on(&cola_write);


			if (down_interruptible (&sem_buffer))
				return -ERESTARTSYS;

			err = rbuffer_insert(buffer, buf_kernel[i]);

			up(&sem_buffer);
			if (err) /* buffer lleno */
			{
				wake_up_interruptible(&cola_read);
				interruptible_sleep_on(&cola_write);
				up(&sem_write);
			}
			else
			{
				i += 1;
				/* despertamos posible consumidor dormido */
				wake_up_interruptible(&cola_read);
			}
		}

		up(&sem_write);
		kfree(buf_kernel);
		return i;
	}
}

static int fifo_release (struct inode *ip, struct file *fp)
{
	dprintk("fifo: Release\n");
	return 0;
}

struct file_operations fifo_ops =
{
	.owner	= 	THIS_MODULE,
	.open  	= 	fifo_open,
	.read	=	fifo_read,
	.write	=	fifo_write,
	.release =	fifo_release
};
